package org.sunspotworld.demo;

import com.sun.spot.util.Queue;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import java.util.*;

public class PlanningThread extends TimerTask
{
    //private EDemoBoard m_demo;
    //private ITriColorLED [] m_leds;
    private Queue m_playQueue;
    private int m_tempo; //bpm
    private float m_key;
    private int m_octave;
    private Object m_synchObject;
    
    private PitchMatrix m_pitchMatrix;
    private RhythmMatrix m_rhythmMatrix;
    private RestMatrix m_restMatrix;
    private ArticulationMatrix m_articuMatrix;
    private Note m_previousNote;
    private int m_previousPitchIndex;
    private int m_previousRhythmIndex;
    private int m_previousRestIndex;
    private int m_previousArticulationIndex;
    private int m_previousCordIndex;
    public static AV localAV = new AV();
    public static AV globalAV = new AV();
    
    public PlanningThread()
    {
        m_playQueue = new Queue();
    }
    
    public PlanningThread(Queue playQueue)
    {
        this();
        setPlayQueue(playQueue);
    }
    
    public PlanningThread(int tempo)
    {
        this();
        setTempo(tempo);
    }
    
    public void setPlayQueue(Queue playQueue)
    {
        m_playQueue = playQueue;
    }
    
    public void setTempo(int tempo)
    {
        m_tempo = tempo;
    }
    
    public void setSynchObject(Object synch)
    {
        m_synchObject = synch;
    }
       
    /**
     * This is run once and woken from sleep when needed?
     * -Assume the audio thread is always playing a measure ahead or whatever,
     * so it can notify the planning thread before the last second.
     */
    public void run()
    {
        m_tempo = 60;
        m_playQueue.put(new Note(Note.PitchType_Rest, getMeasureTime(), 100)); //whole rest
        m_pitchMatrix = new PitchMatrix();
        m_rhythmMatrix = new RhythmMatrix();
        m_restMatrix = new RestMatrix();
        m_articuMatrix = new ArticulationMatrix();
        m_previousRestIndex = 1;
        m_previousPitchIndex = 0;
        m_previousCordIndex = 0;
        m_previousRhythmIndex = m_rhythmMatrix.getSize() - 1;
        m_previousArticulationIndex = m_articuMatrix.getSize() - 1;
        switch(SunSpotAudio.m_role)
        {
            /*case SunSpotAudio.role_harmony:
                m_octave = 1;
                break;
                
            case SunSpotAudio.role_melody:
                m_octave = 2;
                break;*/
                
            default:
                m_octave = 0;
                break;
        }
       //System.out.println("My role: " + SunSpotAudio.m_role + ". My octave: " + m_octave);
        
        while(true)
        {
            planMeasure();
            try
            {
                System.out.println("Planning thread going to sleep.");
                synchronized(m_synchObject)
                {
                    m_synchObject.wait();
                }
            }
            catch(InterruptedException e)
            {
            }
        }
    }
    
    /**
     * Plan out a measure (or more, if the tempo is fast enough) of random notes 
     * based on decision matrices and add it to the play queue.
     */
    private void planMeasure()
    {
        System.out.println("Planning new measure.");
        m_playQueue.put(new Note(Note.PitchType_Marker, 0, 100));
        int timeLeftToFill = getMeasureTime();
        m_previousCordIndex = (m_previousCordIndex + 3)%18;
        
        m_pitchMatrix.initializeAVMatrices(m_previousCordIndex);
        
        while(timeLeftToFill > 0)
        {
            try
            {
                m_previousRestIndex = m_restMatrix.getRandomIndex(m_previousRestIndex);
                m_previousRhythmIndex = m_rhythmMatrix.getRandomIndex(m_previousRhythmIndex);
                m_previousArticulationIndex = m_articuMatrix.getRandomIndex(m_previousArticulationIndex);

                int pitch;
                if(m_previousRestIndex == 1)
                {
                    pitch = Note.PitchType_Rest;
                }
                else
                {
                    m_previousPitchIndex = m_pitchMatrix.getRandomIndex(m_previousPitchIndex);
                    pitch = m_previousPitchIndex + 1 + 12 * m_octave;
                }
                int length = rhythmIndexToLength(m_previousRhythmIndex);
                if(length > timeLeftToFill)
                {
                    length = timeLeftToFill;
                }
                int onTime = 40 + 25 * m_previousArticulationIndex;

                m_previousNote = new Note(pitch, length, onTime);
                m_playQueue.put(m_previousNote);
                timeLeftToFill -= length;
            }
            catch(Exception e)
            {
                System.err.println(e.toString());
            }
        }
        
        ITriColorLED [] m_leds = EDemoBoard.getInstance().getLEDs();
        m_leds[6].setColor(LEDColor.ORANGE);
        m_leds[6].setOn(!m_leds[6].isOn());
        m_leds[7].setColor(LEDColor.ORANGE);
        m_leds[7].setOn(!m_leds[7].isOn());
    }
    
    /**
     * Determine the number of milliseconds in a measure, assuming four-beat
     * measures, given a tempo in bpm.
     * 
     * @return the number of ms in a measure
     */
    public int getMeasureTime()
    {
        //4 beats per measure * 60,000 ms per min / beats per min
        return 4 * 60000 / m_tempo;
    }
    
    /**
     * Determine the number of milliseconds a note should last.
     * 
     * @param rhythmIndex
     * @return
     */
    public int rhythmIndexToLength(int rhythmIndex)
    {
        float divisor;
        switch(rhythmIndex)
        {
            case 0:
                divisor = 16;
                break;
                
            case 1:
                divisor = 8;
                break;
                
            case 2:
                divisor = 8;
                //divisor = 4/.75f;
                break;
                
            case 3:
                divisor = 4;
                break;
                
            case 4:
                divisor = 4;
                //divisor = 4/1.5f;
                
            case 5:
                divisor = 2;
                break;
            
            case 6:
                divisor = 4/3;
                break;
                
            default:
                divisor = 1;
        }
        return (int)(getMeasureTime() / divisor);
    }
    
    
    /*public static void updateLocalAV(float newArousal, float newValence)
    {
        localAV.setValence(newValence);
        localAV.setArousal(newArousal);
        
    }
    
    public static void updateGlobalAV(float newArousal, float newValence)
    {
        globalAV.setValence(newValence);
        globalAV.setArousal(newArousal);
    }*/
}
